﻿namespace Jhong.FunMVC
{
    using System;
    using System.Linq;
    using System.Reflection;

    public class BindParameter
    {
        public Type Type { get; private set; }

        public object Value { get; private set; }


        public BindParameter(Type type, object value)
        {
            this.Type = type;
            this.Value = value;
        }
    }

    internal class ModelBinder
    {
        public static BindParameter[] GetBindParameters(Request request, MethodInfo actionMethod)
        {
            var binder = new ModelBinder();
            binder._request = request;
            binder._actionMethod = actionMethod;
            if (binder.CheckIsViewModel()) return binder.GetBindedModel();
            return binder.GetParameterBind();
        }

        private ModelBinder()
        {
        }

        private Request _request;

        private MethodInfo _actionMethod;

        internal bool CheckIsViewModel()
        {
            var paramters = this._actionMethod.GetParameters();
            return paramters.Count() == 1 && Attribute.GetCustomAttributes(paramters[0].ParameterType, typeof(ViewModelAttribute)).Count() > 0;
        }

        internal BindParameter[] GetBindedModel()
        {
            var vmInstance = Activator.CreateInstance(this._actionMethod.GetParameters()[0].ParameterType);
            var properties = vmInstance.GetType().GetProperties();
            foreach (var p in properties)
            {
                if (null == p.GetSetMethod()) continue;
                if (null == this._request[p.Name]) continue;
                // var value = Convert.ChangeType(this._request[p.Name], p.PropertyType);
                var value = ParameterConvertor.CovertToTargetType(this._request[p.Name], p.PropertyType);
                p.GetSetMethod().Invoke(vmInstance, new object[] { value });
            }
            return new BindParameter[] { new BindParameter(vmInstance.GetType(), vmInstance) };
        }

        internal BindParameter[] GetParameterBind()
        {
            var parameters = this._actionMethod.GetParameters();
            var res = new BindParameter[parameters.Count()];
            for (int i = 0; i < parameters.Count(); i++)
            {
                var p = parameters[i];
                if (null == this._request[p.Name])
                {
                    var code = Type.GetTypeCode(p.ParameterType);
                    res[i] = new BindParameter(p.ParameterType, GetDefault(code));
                    continue;
                }
                //var value = Convert.ChangeType(this._request[p.Name], p.ParameterType);
                var value = ParameterConvertor.CovertToTargetType(this._request[p.Name], p.ParameterType);
                res[i] = new BindParameter(p.ParameterType, value);
            }
            return res;
        }

        internal static object GetDefault(TypeCode code)
        {
            switch (code)
            {
                case TypeCode.UInt64: return default(UInt64);
                case TypeCode.UInt32: return default(UInt32);
                case TypeCode.UInt16: return default(UInt16);
                case TypeCode.String: return default(string);
                case TypeCode.Single: return default(float);
                case TypeCode.SByte: return default(sbyte);
                case TypeCode.Object: return default(object);
                case TypeCode.Int64: return default(Int64);
                case TypeCode.Int32: return default(int);
                case TypeCode.Int16: return default(Int16);
                case TypeCode.Double: return default(double);
                case TypeCode.Decimal: return default(decimal);
                case TypeCode.DateTime: return default(DateTime);
                case TypeCode.Char: return default(char);
                case TypeCode.Byte: return default(byte);
                case TypeCode.Boolean: return default(bool);
                default: return null;
            }
        }
    }

    internal static class ParameterConvertor
    {
        public static object CovertToTargetType(this string input, Type type)
        {
            if (type.IsEnum) return Enum.Parse(type, input);
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                if (false == string.IsNullOrWhiteSpace(input)) return CovertToTargetType(input, type.GetGenericArguments()[0]);
                else return null;
            }
            switch (type.FullName)
            {
                case "System.String": return input;
                case "System.Int32": return input.ToInt32();
                case "System.Int16": return input.ToInt16();
                case "System.Int64": return input.ToInt64();
                case "System.UInt32": return input.ToInt32();
                case "System.UInt16": return input.ToInt16();
                case "System.UInt64": return input.ToInt64();
                case "System.Guid": return input.ToGuid();
                case "System.Char": return input.ToChar();
                case "System.Boolean": return input.ToBool();
                case "System.Single": return input.ToFloat();
                case "System.Double": return input.ToDobule();
                case "System.DateTime": return input.ToDateTime();
                case "System.Byte": return input.ToByte();
                case "System.SByte": return input.ToSByte();
                case "System.Object": return (object)input;
                case "System.Decimal": return input.ToDecimal();
                default: throw new Exception("出现无法转换的未知类型");
            }
        }


        private static char ToChar(this string input)
        {
            try
            {
                return char.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Char类型", input));
            }
        }

        private static DateTime ToDateTime(this string input)
        {
            try
            {
                return DateTime.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为DateTime类型", input));
            }
        }

        private static Guid ToGuid(this string input)
        {
            try
            {
                return Guid.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为GUID类型", input));
            }
        }

        private static int ToInt32(this string input)
        {
            try
            {
                return int.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Int32类型", input));
            }
        }

        private static Int16 ToInt16(this string input)
        {
            try
            {
                return Int16.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Int16类型", input));
            }
        }

        private static Int64 ToInt64(this string input)
        {
            try
            {
                return Int64.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Int64类型", input));
            }
        }

        private static UInt16 ToUInt16(this string input)
        {
            try
            {
                return UInt16.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为UInt16类型", input));
            }
        }

        private static UInt32 ToUInt32(this string input)
        {
            try { return UInt32.Parse(input); }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为UInt32类型", input));
            }
        }

        private static UInt64 ToUInt64(this string input)
        {
            try { return UInt64.Parse(input); }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为UInt64类型", input));
            }
        }

        private static Single ToFloat(this string input)
        {
            try { return float.Parse(input); }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Float类型", input));
            }
        }

        private static double ToDobule(this string input)
        {
            try { return double.Parse(input); }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Dobule类型", input));
            }
        }

        private static decimal ToDecimal(this string input)
        {
            try
            {
                return decimal.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Decimal类型", input));
            }
        }

        private static bool ToBool(this string input)
        {
            try
            {
                return bool.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为Bool类型", input));
            }
        }

        private static byte ToByte(this string input)
        {
            try
            {
                return byte.Parse(input);
            }
            catch { throw new Exception(string.Format("无法将{0}转换为Byte类型", input)); }
        }

        private static sbyte ToSByte(this string input)
        {
            try
            {
                return sbyte.Parse(input);
            }
            catch
            {
                throw new Exception(string.Format("无法将{0}转换为SByte类型", input));
            }
        }

    }
}